using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ShevaEngine.Core.Modules.Animations
{
    /// <summary>
    /// Represents the current pose of a model bone.
    /// </summary>
    public class BonePose
    {
        /// <summary>Used when no animation is set.</summary> 
        public Matrix DefaultMatrix;
        /// <summary>Buffers for interpolation when blending</summary>
        private static Matrix returnMatrix, blendMatrix, currentMatrixBuffer;
        /// <summary>Index.</summary>
        public int BoneIndex { get; private set; }
        /// <summary>The bone name</summary> 
        public string BoneName { get; private set; }
        /// <summary>Parent.</summary>
        public BonePose Parent { get; private set; }
        /// <summary>Current animation.</summary>
        private IAnimationController CurrentAnimation = null;
        /// <summary>Current blend animation.</summary>
        private IAnimationController currentBlendAnimation = null;
        /// <summary>THe amount to interpolate between the current animation and the current blend animation.</summary>
        public float BlendFactor = 0;
        /// <summary>Children.</summary>
        public BonePoseCollection Children { get; private set; }
        /// <summary>True if the current animation contains a track for this bone.</summary> 
        private bool DoesAnimContainChannel = false;
        /// <summary>True if the current blend animation contains a track for this bone.</summary>
        private bool DoesBlendContainChannel = false;


        /// <summary>
        /// Gets or sets the current animation that affects this bone.  If null,
        /// then DefaultTransform will be used for this bone's transform.
        /// </summary>
        public IAnimationController CurrentController
        {
            get { return this.CurrentAnimation; }
            set
            {
                // Don't do anything if the animation hasn't changed
                if (this.CurrentAnimation != value)
                {
                    if (value != null)
                    {
                        if (this.CurrentAnimation != null)
                            this.CurrentAnimation.AnimationTracksChanged -= this.current_AnimationTracksChanged;

                        if (this.BoneName != null)
                        {
                            // Update info on whether or not the current anim
                            // contains a track for this bone
                            this.DoesAnimContainChannel = value.ContainsAnimationTrack(this);
                            value.AnimationTracksChanged += new EventHandler(this.current_AnimationTracksChanged);
                        }
                    }
                    else // A null animation; use defaulttransform
                        this.DoesAnimContainChannel = false;

                    this.CurrentAnimation = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the blend animation that affects this bone.  If the value
        /// is null, then no blending will occur.
        /// </summary>
        public IAnimationController CurrentBlendController
        {
            get { return currentBlendAnimation; }
            set
            {
                // Don't do anything if the animation hasn't changed
                if (currentBlendAnimation != value)
                {

                    if (value != null)
                    {
                        if (currentBlendAnimation != null)
                            currentBlendAnimation.AnimationTracksChanged -= blend_AnimationTracksChanged;
                        if (BoneName != null)
                        {
                            // Update info on whether or not the current anim
                            // contains a track for this bone
                            DoesBlendContainChannel =
                                value.ContainsAnimationTrack(this);
                            value.AnimationTracksChanged += new EventHandler(blend_AnimationTracksChanged);
                        }
                    }
                    else
                        DoesBlendContainChannel = false;
                    currentBlendAnimation = value;
                }
            }
        }


        /// <summary>
        /// Internal creation.
        /// </summary>
        /// <param name="bone"></param>
        /// <param name="bones"></param>
        /// <param name="anims"></param>
        internal BonePose(ModelBone bone, 
            ModelBoneCollection bones,
            BonePose[] anims)
        {
            // Set the values according to the bone
            this.BoneIndex = bone.Index;
            this.BoneName = bone.Name;
            this.DefaultMatrix = bone.Transform;

            if (bone.Parent != null)
                Parent = anims[bone.Parent.Index];
           
            anims[BoneIndex] = this;

            // Recurse on children
            List<BonePose> childList = new List<BonePose>();
            
            foreach (ModelBone child in bone.Children)
            {
                BonePose newChild = new BonePose(
                    bones[child.Index],
                    bones,
                    anims);
                
                childList.Add(newChild);
            }
            
            this.Children = new BonePoseCollection(childList);
        }        

        /// <summary>
        /// Finds the hierarchy for which this bone is the root.
        /// </summary>
        /// <param name="poses"></param>
        private void FindHierarchy(List<BonePose> poses)
        {
            poses.Add(this);
            foreach (BonePose child in Children)
            {
                child.FindHierarchy(poses);
            }
        }

        /// <summary>
        /// Finds a collection of bones that represents the tree of BonePoses with
        /// the current BonePose as the root.
        /// </summary>
        public BonePoseCollection GetHierarchy()
        {
            List<BonePose> poses = new List<BonePose>();
            FindHierarchy(poses);
            return new BonePoseCollection(poses);
        }             

        /// <summary>
        /// Current animation tracks changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void current_AnimationTracksChanged(object sender, EventArgs e)
        {
            this.DoesAnimContainChannel = this.CurrentAnimation.ContainsAnimationTrack(this);
        }

        /// <summary>
        /// Blend animation tracks changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void blend_AnimationTracksChanged(object sender, EventArgs e)
        {
            this.DoesBlendContainChannel = this.currentBlendAnimation.ContainsAnimationTrack(this);
        }        

        /// <summary>
        /// Calculates the current transform, based on the animations, for the bone
        /// represented by the BonePose object.
        /// </summary>
        public Matrix GetCurrentTransform()
        {
 
            // If the bone is not currently affected by an animation
            if (CurrentAnimation == null || !DoesAnimContainChannel)
            {
                // If the bone is affected by a blend animation,
                // blend the defaultTransform with the blend animation
                if (currentBlendAnimation != null && DoesBlendContainChannel)
                {
                    blendMatrix = currentBlendAnimation.GetCurrentBoneTransform(this);
                    Util.SlerpMatrix(
                        ref DefaultMatrix, 
                        ref blendMatrix, 
                        BlendFactor,
                        out returnMatrix);
                }
                    // else return the default transform
                else
                    return DefaultMatrix;
            }
                // The bone is affected by an animation
            else
            {
                // Find the current transform in the animation for the bone
                currentMatrixBuffer = CurrentAnimation.GetCurrentBoneTransform(this);
                // If the bone is affected by a blend animation, blend the
                // current animation transform with the current blend animation
                // transform
                if (currentBlendAnimation != null && DoesBlendContainChannel)
                {
                    blendMatrix = currentBlendAnimation.GetCurrentBoneTransform(this);
                    Util.SlerpMatrix(
                        ref currentMatrixBuffer,
                        ref blendMatrix, 
                        BlendFactor,
                        out returnMatrix);
                }
                    // Else just return the current animation transform
                else
                    return currentMatrixBuffer;
            }
            
            return returnMatrix;            
        }
    }
}
